അസിങ്ക് ഇറ്ററേറ്ററുകൾ ഉപയോഗിച്ച് ജാവാസ്ക്രിപ്റ്റിൽ ഉയർന്ന ത്രൂപുട്ട് പാരലൽ പ്രോസസർ നിർമ്മിക്കാൻ പഠിക്കുക. ഡാറ്റാ-ഇന്റൻസീവ് ആപ്ലിക്കേഷനുകൾ വേഗത്തിലാക്കാൻ കൺകറന്റ് സ്ട്രീം കൈകാര്യം ചെയ്യുന്നതിൽ വൈദഗ്ദ്ധ്യം നേടുക.
ഉയർന്ന പ്രകടനം നൽകുന്ന ജാവാസ്ക്രിപ്റ്റ്: കൺകറന്റ് സ്ട്രീം കൈകാര്യം ചെയ്യുന്നതിനുള്ള ഇറ്ററേറ്റർ ഹെൽപ്പർ പാരലൽ പ്രോസസ്സറുകളിലേക്ക് ഒരു ആഴത്തിലുള്ള പഠനം
ആധുനിക സോഫ്റ്റ്വെയർ വികസന ലോകത്ത്, പ്രകടനം ഒരു സവിശേഷതയല്ല; അത് അടിസ്ഥാനപരമായ ഒരാവശ്യമാണ്. ബാക്കെൻഡ് സേവനങ്ങളിൽ വലിയ ഡാറ്റാസെറ്റുകൾ പ്രോസസ്സ് ചെയ്യുന്നതുമുതൽ ഒരു വെബ് ആപ്ലിക്കേഷനിലെ സങ്കീർണ്ണമായ API ഇടപെടലുകൾ കൈകാര്യം ചെയ്യുന്നതുവരെ, അസിൻക്രണസ് പ്രവർത്തനങ്ങൾ കാര്യക്ഷമമായി കൈകാര്യം ചെയ്യാനുള്ള കഴിവ് പ്രധാനമാണ്. സിംഗിൾ-ത്രെഡഡ്, ഇവൻ്റ്-ഡ്രൈവൺ മോഡൽ ഉള്ള ജാവാസ്ക്രിപ്റ്റ്, I/O-ബൗണ്ട് ടാസ്ക്കുകളിൽ ദീർഘകാലമായി മികച്ചതാണ്. എന്നിരുന്നാലും, ഡാറ്റാ വോളിയം വർദ്ധിക്കുമ്പോൾ, പരമ്പരാഗത സീക്വൻഷ്യൽ പ്രോസസ്സിംഗ് രീതികൾ പ്രധാന തടസ്സങ്ങളായി മാറുന്നു.
10,000 ഉൽപ്പന്നങ്ങളുടെ വിവരങ്ങൾ ശേഖരിക്കേണ്ടതും, ഒരു ജിഗാബൈറ്റ് വലുപ്പമുള്ള ലോഗ് ഫയൽ പ്രോസസ്സ് ചെയ്യേണ്ടതും, അല്ലെങ്കിൽ ഉപയോക്താക്കൾ അപ്ലോഡ് ചെയ്ത നൂറുകണക്കിന് ചിത്രങ്ങൾക്ക് തമ്പ്നയിലുകൾ ഉണ്ടാക്കേണ്ടതും സങ്കൽപ്പിക്കുക. ഈ ടാസ്ക്കുകൾ ഓരോന്നായി കൈകാര്യം ചെയ്യുന്നത് വിശ്വസനീയമാണെങ്കിലും വളരെ സാവധാനത്തിലാണ് നടക്കുക. മികച്ച പ്രകടന നേട്ടങ്ങൾ അൺലോക്ക് ചെയ്യുന്നതിനുള്ള താക്കോൽ കൺകറൻസിയിലാണ്—ഒരേ സമയം ഒന്നിലധികം കാര്യങ്ങൾ പ്രോസസ്സ് ചെയ്യുക. അസിൻക്രണസ് ഇറ്ററേറ്ററുകളുടെ ശക്തിയും, ഒരു കസ്റ്റം പാരലൽ പ്രോസസ്സിംഗ് തന്ത്രവും ചേരുമ്പോൾ ഡാറ്റാ സ്ട്രീമുകൾ കൈകാര്യം ചെയ്യുന്ന രീതി മാറുന്നു.
ഈ സമഗ്രമായ ഗൈഡ്, അടിസ്ഥാനപരമായ `async/await` ലൂപ്പുകൾക്കപ്പുറം പോകാൻ ആഗ്രഹിക്കുന്ന ഇൻ്റർമീഡിയറ്റ് മുതൽ അഡ്വാൻസ്ഡ് വരെയുള്ള ജാവാസ്ക്രിപ്റ്റ് ഡെവലപ്പർമാർക്കുള്ളതാണ്. ഞങ്ങൾ ജാവാസ്ക്രിപ്റ്റ് ഇറ്ററേറ്ററുകളുടെ അടിസ്ഥാനങ്ങൾ പരിശോധിക്കുകയും, സീക്വൻഷ്യൽ തടസ്സങ്ങളെക്കുറിച്ച് പഠിക്കുകയും, ഏറ്റവും പ്രധാനമായി, ആദ്യം മുതൽ ഒരു ശക്തവും, പുനരുപയോഗിക്കാവുന്നതുമായ ഇറ്ററേറ്റർ ഹെൽപ്പർ പാരലൽ പ്രോസസ്സർ നിർമ്മിക്കുകയും ചെയ്യും. ഈ ഉപകരണം ഏത് ഡാറ്റാ സ്ട്രീമിലും കൺകറന്റ് ടാസ്ക്കുകൾ കൃത്യമായ നിയന്ത്രണങ്ങളോടെ കൈകാര്യം ചെയ്യാൻ നിങ്ങളെ സഹായിക്കും, നിങ്ങളുടെ ആപ്ലിക്കേഷനുകൾ വേഗമേറിയതും, കൂടുതൽ കാര്യക്ഷമവും, സ്കേലബിളും ആക്കി മാറ്റും.
അടിസ്ഥാനകാര്യങ്ങൾ മനസ്സിലാക്കുന്നു: ഇറ്ററേറ്ററുകളും അസിൻക്രണസ് ജാവാസ്ക്രിപ്റ്റും
നമ്മുടെ പാരലൽ പ്രോസസ്സർ നിർമ്മിക്കുന്നതിന് മുമ്പ്, അതിന് സാധ്യമാക്കുന്ന ജാവാസ്ക്രിപ്റ്റിന്റെ അടിസ്ഥാന ആശയങ്ങളെക്കുറിച്ച് നമുക്ക് വ്യക്തമായ ധാരണയുണ്ടായിരിക്കണം: ഇറ്ററേറ്റർ പ്രോട്ടോക്കോളുകളും അവയുടെ അസിൻക്രണസ് രൂപങ്ങളും.
ഇറ്ററേറ്ററുകളുടെയും ഇറ്ററബിളുകളുടെയും ശക്തി
ഇറ്ററേറ്റർ പ്രോട്ടോക്കോൾ അടിസ്ഥാനപരമായി ഒരു കൂട്ടം മൂല്യങ്ങൾ ഉൽപ്പാദിപ്പിക്കുന്നതിനുള്ള ഒരു സ്റ്റാൻഡേർഡ് മാർഗ്ഗം നൽകുന്നു. ഒരു ഒബ്ജക്റ്റ് ഇറ്ററബിൾ എന്ന് കണക്കാക്കപ്പെടുന്നത് അത് `Symbol.iterator` എന്ന കീ ഉപയോഗിച്ച് ഒരു മെത്തേഡ് നടപ്പിലാക്കുമ്പോഴാണ്. ഈ മെത്തേഡ് ഒരു ഇറ്ററേറ്റർ ഒബ്ജക്റ്റ് തിരികെ നൽകുന്നു, അതിന് ഒരു `next()` മെത്തേഡ് ഉണ്ട്. `next()` എന്നതിലേക്കുള്ള ഓരോ കോളും രണ്ട് പ്രോപ്പർട്ടികളുള്ള ഒരു ഒബ്ജക്റ്റ് നൽകുന്നു: `value` (ശ്രേണിയിലെ അടുത്ത മൂല്യം) കൂടാതെ `done` (ശ്രേണി പൂർത്തിയായെങ്കിൽ സൂചിപ്പിക്കുന്ന ഒരു ബൂളിയൻ).
ഈ പ്രോട്ടോക്കോൾ `for...of` ലൂപ്പിന് പിന്നിലെ മാന്ത്രികതയാണ്, കൂടാതെ നിരവധി ബിൽറ്റ്-ഇൻ ടൈപ്പുകൾ ഇത് നേറ്റീവായി നടപ്പിലാക്കുന്നു:
- അറേകൾ: `['a', 'b', 'c']`
- സ്ട്രിംഗുകൾ: `"hello"`
- മാപ്പുകൾ: `new Map([['key1', 'value1'], ['key2', 'value2']])`
- സെറ്റുകൾ: `new Set([1, 2, 3])`
ഇറ്ററബിളുകളുടെ സൗന്ദര്യം അവ ഡാറ്റാ സ്ട്രീമുകളെ "ലേസി" രീതിയിൽ പ്രതിനിധീകരിക്കുന്നു എന്നതാണ്. നിങ്ങൾ ഓരോ മൂല്യവും ഓരോന്നായി വലിച്ചെടുക്കുന്നു, ഇത് വലുതോ അനന്തമോ ആയ ശ്രേണികൾക്ക് അവിശ്വസനീയമാംവിധം മെമ്മറി കാര്യക്ഷമമാണ്, കാരണം മുഴുവൻ ഡാറ്റാസെറ്റും ഒരേ സമയം മെമ്മറിയിൽ സൂക്ഷിക്കേണ്ടതില്ല.
അസിങ്ക് ഇറ്ററേറ്ററുകളുടെ ആവിർഭാവം
സ്റ്റാൻഡേർഡ് ഇറ്ററേറ്റർ പ്രോട്ടോക്കോൾ സിൻക്രണസ് ആണ്. നമ്മുടെ ശ്രേണിയിലെ മൂല്യങ്ങൾ ഉടനടി ലഭ്യമല്ലെങ്കിൽ എന്തുചെയ്യും? അവ ഒരു നെറ്റ്വർക്ക് അഭ്യർത്ഥനയിൽ നിന്നോ, ഒരു ഡാറ്റാബേസ് കഴ്സറിൽ നിന്നോ, അല്ലെങ്കിൽ ഒരു ഫയൽ സ്ട്രീമിൽ നിന്നോ ആണെങ്കിലോ? ഇവിടെയാണ് അസിൻക്രണസ് ഇറ്ററേറ്ററുകൾ കടന്നുവരുന്നത്.
അസിങ്ക് ഇറ്ററേറ്റർ പ്രോട്ടോക്കോൾ അതിൻ്റെ സിൻക്രണസ് പ്രോട്ടോക്കോളിൻ്റെ അടുത്ത ബന്ധുവാണ്. ഒരു ഒബ്ജക്റ്റിന് `Symbol.asyncIterator` എന്ന കീ ഉപയോഗിച്ച് ഒരു മെത്തേഡ് ഉണ്ടെങ്കിൽ അത് അസിങ്ക് ഇറ്ററബിൾ ആണ്. ഈ മെത്തേഡ് ഒരു അസിങ്ക് ഇറ്ററേറ്റർ തിരികെ നൽകുന്നു, അതിൻ്റെ `next()` മെത്തേഡ് പരിചിതമായ `{ value, done }` ഒബ്ജക്റ്റിലേക്ക് പരിഹരിക്കുന്ന ഒരു `Promise` നൽകുന്നു.
ഇത് `for await...of` ലൂപ്പ് ഉപയോഗിച്ച്, കാലക്രമേണ വരുന്ന ഡാറ്റാ സ്ട്രീമുകൾ ഉപയോഗിച്ച് പ്രവർത്തിക്കാൻ നമ്മെ പ്രാപ്തരാക്കുന്നു:
ഉദാഹരണം: കാലതാമസത്തോടെ അക്കങ്ങൾ നൽകുന്ന ഒരു അസിങ്ക് ജനറേറ്റർ.
async function* createDelayedNumberStream() {
for (let i = 1; i <= 5; i++) {
// Simulate a network delay or other async operation
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeStream() {
const numberStream = createDelayedNumberStream();
console.log('Starting consumption...');
// The loop will pause at each 'await' until the next value is ready
for await (const number of numberStream) {
console.log(`Received: ${number}`);
}
console.log('Consumption finished.');
}
// Output will show numbers appearing every 500ms
നോഡ്.ജെഎസ്, ബ്രൗസറുകളിലെ ആധുനിക ഡാറ്റാ പ്രോസസ്സിംഗിന് ഈ പാറ്റേൺ അടിസ്ഥാനപരമാണ്, ഇത് വലിയ ഡാറ്റാ ഉറവിടങ്ങൾ സുഗമമായി കൈകാര്യം ചെയ്യാൻ നമ്മെ അനുവദിക്കുന്നു.
ഇറ്ററേറ്റർ ഹെൽപ്പേഴ്സ് പ്രൊപ്പോസൽ അവതരിപ്പിക്കുന്നു
`for...of` ലൂപ്പുകൾ ശക്തമാണെങ്കിലും, അവ ഇംപെറേറ്റീവും വിശദമായതും ആകാം. അറേകൾക്കായി, `.map()`, `.filter()`, `.reduce()` പോലുള്ള ഡെക്ലറേറ്റീവ് മെത്തേഡുകളുടെ ഒരു വലിയ ശേഖരം നമുക്കുണ്ട്. Iterator Helpers TC39 പ്രൊപ്പോസൽ ഈ അതേ എക്സ്പ്രസ്സീവ് പവർ നേരിട്ട് ഇറ്ററേറ്ററുകളിലേക്ക് കൊണ്ടുവരാൻ ലക്ഷ്യമിടുന്നു.
ഈ പ്രൊപ്പോസൽ `Iterator.prototype`, `AsyncIterator.prototype` എന്നിവയിലേക്ക് മെത്തേഡുകൾ ചേർക്കുന്നു, ഇത് ഏതൊരു ഇറ്ററബിൾ സോഴ്സിലും ആദ്യം ഒരു അറേയിലേക്ക് മാറ്റാതെ തന്നെ പ്രവർത്തനങ്ങൾ ബന്ധിപ്പിക്കാൻ നമ്മെ അനുവദിക്കുന്നു. ഇത് മെമ്മറി കാര്യക്ഷമതയ്ക്കും കോഡിൻ്റെ വ്യക്തതയ്ക്കും ഒരു ഗെയിം-ചേഞ്ചറാണ്.
ഒരു ഡാറ്റാ സ്ട്രീമിനെ ഫിൽട്ടർ ചെയ്യാനും മാപ്പ് ചെയ്യാനുമുള്ള ഈ "മുമ്പും ശേഷവും" സാഹചര്യം പരിഗണിക്കുക:
മുമ്പ് (ഒരു സ്റ്റാൻഡേർഡ് ലൂപ്പ് ഉപയോഗിച്ച്):
async function processData(source) {
const results = [];
for await (const item of source) {
if (item.value > 10) { // filter
const processedItem = await transform(item); // map
results.push(processedItem);
}
}
return results;
}
ശേഷം (പ്രൊപ്പോസ്ഡ് അസിങ്ക് ഇറ്ററേറ്റർ ഹെൽപ്പേഴ്സ് ഉപയോഗിച്ച്):
async function processDataWithHelpers(source) {
const results = await source
.filter(item => item.value > 10)
.map(async item => await transform(item))
.toArray(); // .toArray() is another proposed helper
return results;
}
ഈ പ്രൊപ്പോസൽ എല്ലാ പരിതസ്ഥിതികളിലും ഭാഷയുടെ ഒരു സ്റ്റാൻഡേർഡ് ഭാഗമായിട്ടില്ലെങ്കിലും, അതിൻ്റെ തത്വങ്ങൾ നമ്മുടെ പാരലൽ പ്രോസസ്സറിനായുള്ള ആശയപരമായ അടിസ്ഥാനം രൂപപ്പെടുത്തുന്നു. ഓരോ സമയത്തും ഒരു ഐറ്റം മാത്രം പ്രോസസ്സ് ചെയ്യാതെ, ഒന്നിലധികം `transform` പ്രവർത്തനങ്ങൾ പാരലലായി പ്രവർത്തിക്കുന്ന ഒരു `map`-പോലുള്ള ഓപ്പറേഷൻ സൃഷ്ടിക്കാൻ ഞങ്ങൾ ആഗ്രഹിക്കുന്നു.
തടസ്സം: അസിൻക്രണസ് ലോകത്തിലെ സീക്വൻഷ്യൽ പ്രോസസ്സിംഗ്
`for await...of` ലൂപ്പ് ഒരു മികച്ച ഉപകരണമാണ്, എന്നാൽ അതിന് നിർണ്ണായകമായ ഒരു സവിശേഷതയുണ്ട്: അത് സീക്വൻഷ്യൽ ആണ്. നിലവിലെ ഇനത്തിനായുള്ള `await` പ്രവർത്തനങ്ങൾ പൂർണ്ണമായി പൂർത്തിയാകുന്നതുവരെ അടുത്ത ഇനത്തിനായി ലൂപ്പ് ബോഡി ആരംഭിക്കില്ല. സ്വതന്ത്ര ടാസ്ക്കുകൾ കൈകാര്യം ചെയ്യുമ്പോൾ ഇത് പ്രകടനത്തിന് ഒരു പരിധി നിശ്ചയിക്കുന്നു.
ഒരു സാധാരണ, യഥാർത്ഥ ലോക സാഹചര്യത്തിൽ നമുക്ക് ഇത് വ്യക്തമാക്കാം: ഐഡൻ്റിഫയറുകളുടെ ഒരു ലിസ്റ്റിനായി ഒരു API-യിൽ നിന്ന് ഡാറ്റാ fetch ചെയ്യുന്നത്.
നമ്മൾക്ക് 100 ഉപയോക്തൃ ഐഡികൾ നൽകുന്ന ഒരു അസിങ്ക് ഇറ്ററേറ്റർ ഉണ്ടെന്ന് സങ്കൽപ്പിക്കുക. ഓരോ ഐഡിക്കും, ഉപയോക്താവിൻ്റെ പ്രൊഫൈൽ ലഭിക്കുന്നതിന് നമ്മൾ ഒരു API കോൾ ചെയ്യേണ്ടതുണ്ട്. ഓരോ API കോളിനും ശരാശരി 200 മില്ലിസെക്കൻഡ് എടുക്കുന്നു എന്ന് നമുക്ക് അനുമാനിക്കാം.
async function fetchUserProfile(userId) {
// Simulate an API call
await new Promise(resolve => setTimeout(resolve, 200));
return { id: userId, name: `User ${userId}`, fetchedAt: new Date() };
}
async function fetchAllUsersSequentially(userIds) {
console.time('SequentialFetch');
const profiles = [];
for await (const id of userIds) {
const profile = await fetchUserProfile(id);
profiles.push(profile);
console.log(`Fetched user ${id}`);
}
console.timeEnd('SequentialFetch');
return profiles;
}
// Assuming 'userIds' is an async iterable of 100 IDs
// await fetchAllUsersSequentially(userIds);
മൊത്തം എക്സിക്യൂഷൻ സമയം എത്രയാണ്? ഓരോ `await fetchUserProfile(id)` അടുത്തത് ആരംഭിക്കുന്നതിന് മുമ്പ് പൂർത്തിയാക്കേണ്ടതുകൊണ്ട്, മൊത്തം സമയം ഏകദേശം:
100 ഉപയോക്താക്കൾ * 200 മില്ലിസെക്കൻഡ്/ഉപയോക്താവ് = 20,000 മില്ലിസെക്കൻഡ് (20 സെക്കൻഡ്)
ഇതൊരു ക്ലാസിക് I/O-ബൗണ്ട് തടസ്സമാണ്. നമ്മുടെ ജാവാസ്ക്രിപ്റ്റ് പ്രോസസ്സ് നെറ്റ്വർക്കിനായി കാത്തിരിക്കുമ്പോൾ, അതിൻ്റെ ഇവൻ്റ് ലൂപ്പ് മിക്കവാറും നിഷ്ക്രിയമായിരിക്കും. സിസ്റ്റത്തിൻ്റെയോ എക്സ്റ്റേണൽ API-യുടെയോ പൂർണ്ണ ശേഷി നമ്മൾ ഉപയോഗിക്കുന്നില്ല. പ്രോസസ്സിംഗ് ടൈംലൈൻ ഇങ്ങനെയായിരിക്കും:
ടാസ്ക് 1: [---കാത്തിരിക്കുക---] പൂർത്തിയായി
ടാസ്ക് 2: [---കാത്തിരിക്കുക---] പൂർത്തിയായി
ടാസ്ക് 3: [---കാത്തിരിക്കുക---] പൂർത്തിയായി
...തുടങ്ങി.
10 കൺകറൻസി ലെവൽ ഉപയോഗിച്ച്, ഈ ടൈംലൈൻ ഇങ്ങനെ മാറ്റിയെടുക്കുക എന്നതാണ് നമ്മുടെ ലക്ഷ്യം:
ടാസ്ക് 1-10: [---കാത്തിരിക്കുക---][---കാത്തിരിക്കുക---]... പൂർത്തിയായി
ടാസ്ക് 11-20: [---കാത്തിരിക്കുക---][---കാത്തിരിക്കുക---]... പൂർത്തിയായി
...
10 കൺകറന്റ് പ്രവർത്തനങ്ങളിലൂടെ, നമുക്ക് സൈദ്ധാന്തികമായി മൊത്തം സമയം 20 സെക്കൻഡിൽ നിന്ന് 2 സെക്കൻഡായി കുറയ്ക്കാൻ കഴിയും. നമ്മുടെ സ്വന്തം പാരലൽ പ്രോസസ്സർ നിർമ്മിക്കുന്നതിലൂടെ നമ്മൾ ലക്ഷ്യമിടുന്ന പ്രകടന കുതിച്ചുചാട്ടമാണിത്.
ഒരു ജാവാസ്ക്രിപ്റ്റ് ഇറ്ററേറ്റർ ഹെൽപ്പർ പാരലൽ പ്രോസസ്സർ നിർമ്മിക്കുന്നു
ഇപ്പോൾ നമ്മൾ ഈ ലേഖനത്തിൻ്റെ പ്രധാന ഭാഗത്ത് എത്തിയിരിക്കുന്നു. ഒരു അസിങ്ക് ഇറ്ററബിൾ സോഴ്സ്, ഒരു മാപ്പർ ഫംഗ്ഷൻ, ഒരു കൺകറൻസി ലെവൽ എന്നിവ സ്വീകരിക്കുന്ന `parallelMap` എന്ന് നമ്മൾ വിളിക്കുന്ന ഒരു പുനരുപയോഗിക്കാവുന്ന അസിങ്ക് ജനറേറ്റർ ഫംഗ്ഷൻ നമ്മൾ നിർമ്മിക്കും. ഇത് പ്രോസസ്സ് ചെയ്ത ഫലങ്ങൾ ലഭ്യമാകുമ്പോൾ നൽകുന്ന ഒരു പുതിയ അസിങ്ക് ഇറ്ററബിൾ ഉൽപ്പാദിപ്പിക്കും.
പ്രധാന ഡിസൈൻ തത്വങ്ങൾ
- കൺകറൻസി പരിമിതപ്പെടുത്തൽ: പ്രോസസ്സറിന് ഒരു സമയം ഒരു നിശ്ചിത എണ്ണത്തിൽ കൂടുതൽ `mapper` ഫംഗ്ഷൻ പ്രോമിസുകൾ പ്രവർത്തിക്കാൻ പാടില്ല. റിസോഴ്സുകൾ കൈകാര്യം ചെയ്യുന്നതിനും ബാഹ്യ API റേറ്റ് പരിധികൾ പാലിക്കുന്നതിനും ഇത് നിർണ്ണായകമാണ്.
- ലേസി കൺസംപ്ഷൻ: അതിൻ്റെ പ്രോസസ്സിംഗ് പൂളിൽ ഒരു ഒഴിവുള്ള സ്ഥലം ഉണ്ടെങ്കിൽ മാത്രമേ അത് സോഴ്സ് ഇറ്ററേറ്ററിൽ നിന്ന് വലിച്ചെടുക്കാൻ പാടുള്ളൂ. ഇത് മുഴുവൻ സോഴ്സും മെമ്മറിയിൽ ബഫർ ചെയ്യുന്നില്ലെന്ന് ഉറപ്പാക്കുന്നു, സ്ട്രീമുകളുടെ പ്രയോജനങ്ങൾ നിലനിർത്തുന്നു.
- ബാക്ക്പ്രഷർ കൈകാര്യം ചെയ്യൽ: ഔട്ട്പുട്ടിൻ്റെ ഉപഭോക്താവ് സാവധാനത്തിലാണെങ്കിൽ പ്രോസസ്സർ സ്വാഭാവികമായും താൽക്കാലികമായി നിർത്തണം. അസിങ്ക് ജനറേറ്ററുകൾ `yield` കീവേഡ് വഴി ഇത് യാന്ത്രികമായി നേടുന്നു. `yield`-ൽ എക്സിക്യൂഷൻ താൽക്കാലികമായി നിർത്തുമ്പോൾ, ഉറവിടത്തിൽ നിന്ന് പുതിയ ഇനങ്ങൾ വലിച്ചെടുക്കില്ല.
- പരമാവധി ത്രൂപുട്ടിനായുള്ള ക്രമരഹിതമായ ഔട്ട്പുട്ട്: സാധ്യമായ ഏറ്റവും ഉയർന്ന വേഗത കൈവരിക്കുന്നതിന്, നമ്മുടെ പ്രോസസ്സർ ഇൻപുട്ടിൻ്റെ യഥാർത്ഥ ക്രമത്തിലായിരിക്കണം എന്നില്ല, ഫലങ്ങൾ തയ്യാറാകുമ്പോൾ തന്നെ നൽകും. പിന്നീട് ഒരു അഡ്വാൻസ്ഡ് വിഷയമായി ക്രമം എങ്ങനെ നിലനിർത്താമെന്ന് നമ്മൾ ചർച്ച ചെയ്യും.
`parallelMap` നടപ്പിലാക്കൽ
നമുക്ക് നമ്മുടെ ഫംഗ്ഷൻ ഘട്ടം ഘട്ടമായി നിർമ്മിക്കാം. ഒരു കസ്റ്റം അസിങ്ക് ഇറ്ററേറ്റർ നിർമ്മിക്കുന്നതിനുള്ള ഏറ്റവും മികച്ച ഉപകരണം ഒരു `async function*` (അസിങ്ക് ജനറേറ്റർ) ആണ്.
/**
* Creates a new async iterable that processes items from a source iterable in parallel.
* @param {AsyncIterable|Iterable} source The source iterable to process.
* @param {Function} mapperFn An async function that takes an item and returns a promise of the processed result.
* @param {object} options
* @param {number} options.concurrency The maximum number of tasks to run in parallel.
* @returns {AsyncGenerator} An async generator that yields the processed results.
*/
async function* parallelMap(source, mapperFn, { concurrency = 5 }) {
// 1. Get the async iterator from the source.
// This works for both sync and async iterables.
const asyncIterator = source[Symbol.asyncIterator] ?
source[Symbol.asyncIterator]() :
source[Symbol.iterator]();
// 2. A set to keep track of the promises for the currently processing tasks.
// Using a Set makes adding and deleting promises efficient.
const processing = new Set();
// 3. A flag to track if the source iterator is exhausted.
let sourceIsDone = false;
// 4. The main loop: continues as long as there are tasks processing
// or the source has more items.
while (!sourceIsDone || processing.size > 0) {
// 5. Fill the processing pool up to the concurrency limit.
while (processing.size < concurrency && !sourceIsDone) {
const nextItemPromise = asyncIterator.next();
const processingPromise = nextItemPromise.then(item => {
if (item.done) {
sourceIsDone = true;
return; // Signal that this branch is done, no result to process.
}
// Execute the mapper function and ensure its result is a promise.
// This returns the final processed value.
return Promise.resolve(mapperFn(item.value));
});
// This is a crucial step for managing the pool.
// We create a wrapper promise that, when it resolves, gives us both
// the final result and a reference to itself, so we can remove it from the pool.
const trackedPromise = processingPromise.then(result => ({
result,
origin: trackedPromise
}));
processing.add(trackedPromise);
}
// 6. If the pool is empty, we must be done. Break the loop.
if (processing.size === 0) break;
// 7. Wait for ANY of the processing tasks to complete.
// Promise.race() is the key to achieving this.
const { result, origin } = await Promise.race(processing);
// 8. Remove the completed promise from the processing pool.
processing.delete(origin);
// 9. Yield the result, unless it's the 'undefined' from a 'done' signal.
// This pauses the generator until the consumer requests the next item.
if (result !== undefined) {
yield result;
}
}
}
ലോജിക് വിശദീകരിക്കുന്നു
- ഇനിഷ്യലൈസേഷൻ: സോഴ്സിൽ നിന്ന് അസിങ്ക് ഇറ്ററേറ്റർ എടുത്ത് നമ്മുടെ കൺകറൻസി പൂളായി പ്രവർത്തിക്കാൻ `processing` എന്ന് പേരുള്ള ഒരു `Set` ഇനിഷ്യലൈസ് ചെയ്യുന്നു.
- പൂൾ നിറയ്ക്കുന്നു: ഉള്ളിലെ `while` ലൂപ്പാണ് എഞ്ചിൻ. `processing` സെറ്റിൽ ഇടമുണ്ടോ എന്നും `source`-ൽ ഇനങ്ങൾ ഉണ്ടോ എന്നും ഇത് പരിശോധിക്കുന്നു. ഉണ്ടെങ്കിൽ, അത് അടുത്ത ഇനം വലിച്ചെടുക്കുന്നു.
- ടാസ്ക് എക്സിക്യൂഷൻ: ഓരോ ഇനത്തിനും, നമ്മൾ `mapperFn` വിളിക്കുന്നു. അടുത്ത ഇനം നേടുന്നതും അത് മാപ്പ് ചെയ്യുന്നതും ഉൾപ്പെടെയുള്ള മുഴുവൻ പ്രവർത്തനവും ഒരു പ്രോമിസിൽ (`processingPromise`) പൊതിഞ്ഞിരിക്കുന്നു.
- പ്രോമിസുകൾ ട്രാക്ക് ചെയ്യുന്നു: `Promise.race()` ന് ശേഷം സെറ്റിൽ നിന്ന് ഏത് പ്രോമിസ് നീക്കം ചെയ്യണമെന്ന് അറിയുക എന്നതാണ് ഏറ്റവും തന്ത്രപരമായ ഭാഗം. `Promise.race()` പ്രോമിസ് ഒബ്ജക്റ്റ് അല്ല, പരിഹരിച്ച മൂല്യം ആണ് തിരികെ നൽകുന്നത്. ഇത് പരിഹരിക്കാൻ, അന്തിമ `result` ഉം അതിലേക്കുള്ള ഒരു റഫറൻസും (`origin`) ഉൾക്കൊള്ളുന്ന ഒരു ഒബ്ജക്റ്റിലേക്ക് പരിഹരിക്കുന്ന ഒരു `trackedPromise` നമ്മൾ സൃഷ്ടിക്കുന്നു. ഈ ട്രാക്കിംഗ് പ്രോമിസ് നമ്മുടെ `processing` സെറ്റിലേക്ക് ചേർക്കുന്നു.
- ഏറ്റവും വേഗതയേറിയ ടാസ്കിനായി കാത്തിരിക്കുന്നു: `await Promise.race(processing)` പൂളിലെ ആദ്യ ടാസ്ക് പൂർത്തിയാകുന്നത് വരെ എക്സിക്യൂഷൻ താൽക്കാലികമായി നിർത്തുന്നു. ഇതാണ് നമ്മുടെ കൺകറൻസി മോഡലിൻ്റെ കാതൽ.
- ഫലം നൽകുകയും വീണ്ടും നിറയ്ക്കുകയും ചെയ്യുന്നു: ഒരു ടാസ്ക് പൂർത്തിയായാൽ, അതിൻ്റെ ഫലം നമ്മൾക്ക് ലഭിക്കും. അതിൻ്റെ അനുബന്ധ `trackedPromise` നമ്മൾ `processing` സെറ്റിൽ നിന്ന് നീക്കംചെയ്യുന്നു, ഇത് ഒരു സ്ലോട്ട് സ്വതന്ത്രമാക്കുന്നു. തുടർന്ന് നമ്മൾ ഫലം `yield` ചെയ്യുന്നു. കൺസ്യൂമറിൻ്റെ ലൂപ്പ് അടുത്ത ഇനം ആവശ്യപ്പെടുമ്പോൾ, നമ്മുടെ പ്രധാന `while` ലൂപ്പ് തുടരുന്നു, കൂടാതെ ഉള്ളിലെ `while` ലൂപ്പ് സോഴ്സിൽ നിന്ന് ഒരു പുതിയ ടാസ്ക് ഉപയോഗിച്ച് ഒഴിഞ്ഞ സ്ലോട്ട് നിറയ്ക്കാൻ ശ്രമിക്കും.
നമ്മുടെ `parallelMap` ഉപയോഗിക്കുന്നു
നമ്മുടെ ഉപയോക്താക്കളെ fetch ചെയ്യുന്ന ഉദാഹരണവും പുതിയ യൂട്ടിലിറ്റിയും നമുക്ക് വീണ്ടും പരിശോധിക്കാം.
// Assume 'createIdStream' is an async generator yielding 100 user IDs.
const userIdStream = createIdStream();
async function fetchAllUsersInParallel() {
console.time('ParallelFetch');
const profilesStream = parallelMap(userIdStream, fetchUserProfile, { concurrency: 10 });
for await (const profile of profilesStream) {
console.log(`Processed profile for user ${profile.id}`);
}
console.timeEnd('ParallelFetch');
}
// await fetchAllUsersInParallel();
10-ൻ്റെ കൺകറൻസി ഉപയോഗിച്ച്, മൊത്തം എക്സിക്യൂഷൻ സമയം 20-ന് പകരം ഏകദേശം 2 സെക്കൻഡ് ആയിരിക്കും. `parallelMap` ഉപയോഗിച്ച് നമ്മുടെ സ്ട്രീമിനെ പൊതിഞ്ഞതിലൂടെ നമ്മൾക്ക് 10 മടങ്ങ് പ്രകടന മെച്ചപ്പെടുത്തൽ നേടാനായി. ഉപയോഗിക്കുന്ന കോഡ് ലളിതവും വായിക്കാൻ എളുപ്പമുള്ളതുമായ `for await...of` ലൂപ്പായി തുടരുന്നു എന്നതാണ് ഇതിൻ്റെ സൗന്ദര്യം.
പ്രായോഗിക ഉപയോഗ കേസുകളും ആഗോള ഉദാഹരണങ്ങളും
ഈ പാറ്റേൺ ഉപയോക്തൃ ഡാറ്റാ fetch ചെയ്യുന്നതിന് മാത്രമുള്ളതല്ല. ആഗോള ആപ്ലിക്കേഷൻ ഡെവലപ്മെൻ്റിൽ സാധാരണയായി കാണുന്ന നിരവധി പ്രശ്നങ്ങൾക്ക് ഇത് പ്രായോഗികമായ ഒരു ഉപകരണമാണ്.
ഉയർന്ന ത്രൂപുട്ട് API ഇടപെടലുകൾ
സാഹചര്യം: ഒരു സാമ്പത്തിക സേവന ആപ്ലിക്കേഷന് ഒരു കൂട്ടം ട്രാൻസാക്ഷൻ ഡാറ്റയെ മെച്ചപ്പെടുത്തേണ്ടതുണ്ട്. ഓരോ ട്രാൻസാക്ഷനും, അത് രണ്ട് ബാഹ്യ API-കൾ വിളിക്കണം: ഒന്ന് തട്ടിപ്പ് കണ്ടെത്തലിനും മറ്റൊന്ന് കറൻസി മാറ്റുന്നതിനും. ഈ API-കൾക്ക് സെക്കൻഡിൽ 100 അഭ്യർത്ഥനകൾ എന്ന നിരക്ക് പരിധിയുണ്ട്.
പരിഹാരം: ട്രാൻസാക്ഷനുകളുടെ സ്ട്രീം പ്രോസസ്സ് ചെയ്യുന്നതിന് `20` അല്ലെങ്കിൽ `30` എന്ന `concurrency` ക്രമീകരണത്തോടെ `parallelMap` ഉപയോഗിക്കുക. `mapperFn` `Promise.all` ഉപയോഗിച്ച് രണ്ട് API കോളുകൾ ചെയ്യും. കൺകറൻസി പരിധി, API റേറ്റ് പരിധികൾ ലംഘിക്കാതെ ഉയർന്ന ത്രൂപുട്ട് ലഭിക്കുന്നുവെന്ന് ഉറപ്പാക്കുന്നു, ഇത് മൂന്നാം കക്ഷി സേവനങ്ങളുമായി സംവദിക്കുന്ന ഏതൊരു ആപ്ലിക്കേഷനും ഒരു പ്രധാന ആശങ്കയാണ്.
വലിയ തോതിലുള്ള ഡാറ്റാ പ്രോസസ്സിംഗും ETL-ഉം (എക്സ്ട്രാക്റ്റ്, ട്രാൻസ്ഫോം, ലോഡ്)
സാഹചര്യം: ഒരു നോഡ്.ജെഎസ് പരിതസ്ഥിതിയിലെ ഒരു ഡാറ്റാ അനലിറ്റിക്സ് പ്ലാറ്റ്ഫോമിന് ഒരു ക്ലൗഡ് ബക്കറ്റിൽ (Amazon S3 അല്ലെങ്കിൽ Google Cloud Storage പോലെ) സൂക്ഷിച്ചിരിക്കുന്ന 5GB CSV ഫയൽ പ്രോസസ്സ് ചെയ്യേണ്ടതുണ്ട്. ഓരോ നിരയും വാലിഡേറ്റ് ചെയ്യുകയും, ക്ലീൻ ചെയ്യുകയും, ഒരു ഡാറ്റാബേസിൽ ചേർക്കുകയും ചെയ്യേണ്ടതുണ്ട്.
പരിഹാരം: ക്ലൗഡ് സ്റ്റോറേജ് സ്ട്രീമിൽ നിന്ന് ഫയൽ ലൈൻ-ബൈ-ലൈനായി വായിക്കുന്ന ഒരു അസിങ്ക് ഇറ്ററേറ്റർ ഉണ്ടാക്കുക (ഉദാഹരണത്തിന്, നോഡ്.ജെഎസിലെ `stream.Readable` ഉപയോഗിച്ച്). ഈ ഇറ്ററേറ്ററിനെ `parallelMap` ലേക്ക് പൈപ്പ് ചെയ്യുക. `mapperFn` വാലിഡേഷൻ ലോജിക്കും ഡാറ്റാബേസ് `INSERT` ഓപ്പറേഷനും നടത്തും. ഡാറ്റാബേസിൻ്റെ കണക്ഷൻ പൂൾ വലുപ്പത്തെ അടിസ്ഥാനമാക്കി `concurrency` ക്രമീകരിക്കാൻ കഴിയും. ഈ സമീപനം 5GB ഫയൽ മെമ്മറിയിലേക്ക് ലോഡ് ചെയ്യുന്നത് ഒഴിവാക്കുകയും പൈപ്പ്ലൈനിലെ വേഗത കുറഞ്ഞ ഡാറ്റാബേസ് ഇൻസേർഷൻ ഭാഗത്തെ പാരലലൈസ് ചെയ്യുകയും ചെയ്യുന്നു.
ചിത്രവും വീഡിയോയും ട്രാൻസ്കോഡിംഗ് പൈപ്പ്ലൈൻ
സാഹചര്യം: ഒരു ആഗോള സോഷ്യൽ മീഡിയ പ്ലാറ്റ്ഫോം ഉപയോക്താക്കൾക്ക് വീഡിയോകൾ അപ്ലോഡ് ചെയ്യാൻ അനുവദിക്കുന്നു. ഓരോ വീഡിയോയും ഒന്നിലധികം റെസല്യൂഷനുകളിലേക്ക് (ഉദാഹരണത്തിന്, 1080p, 720p, 480p) ട്രാൻസ്കോഡ് ചെയ്യണം. ഇതൊരു CPU-ഇന്റൻസീവ് ടാസ്ക്കാണ്.
പരിഹാരം: ഒരു ഉപയോക്താവ് ഒരു ബാച്ച് വീഡിയോകൾ അപ്ലോഡ് ചെയ്യുമ്പോൾ, വീഡിയോ ഫയൽ പാതകളുടെ ഒരു ഇറ്ററേറ്റർ ഉണ്ടാക്കുക. `mapperFn` ഒരു അസിങ്ക് ഫംഗ്ഷൻ ആകാം, അത് `ffmpeg` പോലുള്ള ഒരു കമാൻഡ്-ലൈൻ ടൂൾ പ്രവർത്തിപ്പിക്കാൻ ഒരു ചൈൽഡ് പ്രോസസ്സ് സ്പോൺ ചെയ്യുന്നു. സിസ്റ്റം ഓവർലോഡ് ചെയ്യാതെ ഹാർഡ്വെയർ വിനിയോഗം പരമാവധിയാക്കാൻ മെഷീനിലെ ലഭ്യമായ CPU കോറുകളുടെ എണ്ണത്തിന് തുല്യമായി `concurrency` സജ്ജീകരിക്കണം (ഉദാഹരണത്തിന്, നോഡ്.ജെഎസിലെ `os.cpus().length`).
വിപുലമായ ആശയങ്ങളും പരിഗണനകളും
നമ്മുടെ `parallelMap` ശക്തമാണെങ്കിലും, യഥാർത്ഥ ലോക ആപ്ലിക്കേഷനുകൾക്ക് പലപ്പോഴും കൂടുതൽ സൂക്ഷ്മത ആവശ്യമാണ്.
ശക്തമായ എറർ കൈകാര്യം ചെയ്യൽ
`mapperFn` കോളുകളിൽ ഒന്ന് നിരസിച്ചാൽ എന്ത് സംഭവിക്കും? നമ്മുടെ നിലവിലെ നടപ്പാക്കലിൽ, `Promise.race` നിരസിക്കപ്പെടും, ഇത് മുഴുവൻ `parallelMap` ജനറേറ്ററും ഒരു പിശക് എറിയാനും പ്രവർത്തനം അവസാനിപ്പിക്കാനും ഇടയാക്കും. ഇതൊരു "ഫെയിൽ-ഫാസ്റ്റ്" തന്ത്രമാണ്.
പലപ്പോഴും, വ്യക്തിഗത പരാജയങ്ങളെ അതിജീവിക്കാൻ കഴിയുന്ന കൂടുതൽ പ്രതിരോധശേഷിയുള്ള ഒരു പൈപ്പ്ലൈൻ നിങ്ങൾക്ക് ആവശ്യമാണ്. നിങ്ങളുടെ `mapperFn` പൊതിഞ്ഞുകൊണ്ട് ഇത് നേടാനാകും.
const resilientMapper = async (item) => {
try {
return { status: 'fulfilled', value: await originalMapper(item) };
} catch (error) {
console.error(`Failed to process item ${item.id}:`, error);
return { status: 'rejected', reason: error, item: item };
}
};
const resultsStream = parallelMap(source, resilientMapper, { concurrency: 10 });
for await (const result of resultsStream) {
if (result.status === 'fulfilled') {
// process successful value
} else {
// handle or log the failure
}
}
ക്രമം നിലനിർത്തുന്നു
നമ്മുടെ `parallelMap` വേഗതയ്ക്ക് മുൻഗണന നൽകി, ക്രമരഹിതമായി ഫലങ്ങൾ നൽകുന്നു. ചിലപ്പോൾ, ഔട്ട്പുട്ടിൻ്റെ ക്രമം ഇൻപുട്ടിൻ്റെ ക്രമവുമായി പൊരുത്തപ്പെടണം. ഇതിന് വ്യത്യസ്തവും കൂടുതൽ സങ്കീർണ്ണവുമായ ഒരു നടപ്പാക്കൽ ആവശ്യമാണ്, ഇതിനെ പലപ്പോഴും `parallelOrderedMap` എന്ന് വിളിക്കുന്നു.
ഒരു ഓർഡർ ചെയ്ത പതിപ്പിനായുള്ള പൊതുവായ തന്ത്രം ഇതാണ്:
- മുമ്പത്തെപ്പോലെ ഇനങ്ങൾ പാരലലായി പ്രോസസ്സ് ചെയ്യുക.
- ഉടനടി ഫലങ്ങൾ നൽകുന്നതിന് പകരം, അവയെ അവയുടെ യഥാർത്ഥ ഇൻഡെക്സ് ഉപയോഗിച്ച് ഒരു ബഫറിലോ മാപ്പിലോ സൂക്ഷിക്കുക.
- നൽകേണ്ട അടുത്ത പ്രതീക്ഷിക്കുന്ന ഇൻഡെക്സിനായി ഒരു കൗണ്ടർ പരിപാലിക്കുക.
- ഒരു ലൂപ്പിൽ, നിലവിൽ പ്രതീക്ഷിക്കുന്ന ഇൻഡെക്സിനായുള്ള ഫലം ബഫറിൽ ലഭ്യമാണോ എന്ന് പരിശോധിക്കുക. ലഭ്യമാണെങ്കിൽ, അത് നൽകുക, കൗണ്ടർ വർദ്ധിപ്പിക്കുക, ആവർത്തിക്കുക. ഇല്ലെങ്കിൽ, കൂടുതൽ ടാസ്ക്കുകൾ പൂർത്തിയാകാൻ കാത്തിരിക്കുക.
ഇത് ബഫറിനായി ഓവർഹെഡും മെമ്മറി ഉപയോഗവും വർദ്ധിപ്പിക്കുന്നു, എന്നാൽ ഓർഡർ ആശ്രിത വർക്ക്ഫ്ലോകൾക്ക് ഇത് അത്യാവശ്യമാണ്.
ബാക്ക്പ്രഷർ വിശദീകരിക്കുന്നു
ഈ അസിങ്ക് ജനറേറ്റർ അധിഷ്ഠിത സമീപനത്തിൻ്റെ ഏറ്റവും മികച്ച സവിശേഷതകളിലൊന്ന് ആവർത്തിച്ച് പറയേണ്ടതുണ്ട്: ഓട്ടോമാറ്റിക് ബാക്ക്പ്രഷർ കൈകാര്യം ചെയ്യൽ. നമ്മുടെ `parallelMap` ഉപയോഗിക്കുന്ന കോഡ് വേഗത കുറഞ്ഞതാണെങ്കിൽ—ഉദാഹരണത്തിന്, ഓരോ ഫലവും വേഗത കുറഞ്ഞ ഡിസ്കിലേക്കോ തിരക്കേറിയ നെറ്റ്വർക്ക് സോക്കറ്റിലേക്കോ എഴുതുകയാണെങ്കിൽ—`for await...of` ലൂപ്പ് അടുത്ത ഇനം ആവശ്യപ്പെടില്ല. ഇത് നമ്മുടെ ജനറേറ്ററിനെ `yield result;` എന്ന വരിയിൽ താൽക്കാലികമായി നിർത്താൻ കാരണമാകുന്നു. താൽക്കാലികമായി നിർത്തിയിരിക്കുമ്പോൾ, അത് ലൂപ്പ് ചെയ്യുന്നില്ല, `Promise.race` വിളിക്കുന്നില്ല, ഏറ്റവും പ്രധാനമായി, അത് പ്രോസസ്സിംഗ് പൂൾ നിറയ്ക്കുന്നില്ല. ഈ ആവശ്യകതയുടെ കുറവ് യഥാർത്ഥ ഉറവിട ഇറ്ററേറ്ററിലേക്ക് തിരികെ വ്യാപിക്കുന്നു, അതിൽ നിന്ന് വായിക്കപ്പെടുന്നില്ല. മുഴുവൻ പൈപ്പ്ലൈനും അതിൻ്റെ ഏറ്റവും വേഗത കുറഞ്ഞ ഘടകത്തിൻ്റെ വേഗതയുമായി പൊരുത്തപ്പെടാൻ സ്വയമേവ വേഗത കുറയ്ക്കുന്നു, അമിതമായി ബഫർ ചെയ്യുന്നത് മൂലമുണ്ടാകുന്ന മെമ്മറി തകർച്ച തടയുന്നു.
ഉപസംഹാരവും ഭാവി കാഴ്ചപ്പാടും
ജാവാസ്ക്രിപ്റ്റ് ഇറ്ററേറ്ററുകളുടെ അടിസ്ഥാന ആശയങ്ങളിൽ നിന്ന് ആരംഭിച്ച്, സങ്കീർണ്ണവും ഉയർന്ന പ്രകടനം നൽകുന്നതുമായ ഒരു പാരലൽ പ്രോസസ്സിംഗ് യൂട്ടിലിറ്റി നിർമ്മിക്കുന്നതിലേക്ക് നമ്മൾ എത്തിച്ചേർന്നു. സീക്വൻഷ്യൽ `for await...of` ലൂപ്പുകളിൽ നിന്ന് ഒരു നിയന്ത്രിത കൺകറന്റ് മോഡലിലേക്ക് മാറിയതിലൂടെ, ഡാറ്റാ-ഇന്റൻസീവ്, I/O-ബൗണ്ട്, CPU-ബൗണ്ട് ടാസ്ക്കുകൾക്കായി എങ്ങനെ വലിയ തോതിലുള്ള പ്രകടന മെച്ചപ്പെടുത്തലുകൾ നേടാമെന്ന് നമ്മൾ വിശദീകരിച്ചു.
പ്രധാന പഠനങ്ങൾ ഇവയാണ്:
- സീക്വൻഷ്യൽ വേഗത കുറഞ്ഞതാണ്: പരമ്പരാഗത അസിങ്ക് ലൂപ്പുകൾ സ്വതന്ത്ര ടാസ്ക്കുകൾക്ക് ഒരു തടസ്സമാണ്.
- കൺകറൻസി പ്രധാനമാണ്: ഇനങ്ങൾ പാരലലായി പ്രോസസ്സ് ചെയ്യുന്നത് മൊത്തം എക്സിക്യൂഷൻ സമയം ഗണ്യമായി കുറയ്ക്കുന്നു.
- അസിങ്ക് ജനറേറ്ററുകൾ മികച്ച ഉപകരണങ്ങളാണ്: ബാക്ക്പ്രഷർ പോലുള്ള നിർണ്ണായക സവിശേഷതകൾക്ക് ബിൽറ്റ്-ഇൻ പിന്തുണയോടെ കസ്റ്റം ഇറ്ററബിളുകൾ നിർമ്മിക്കുന്നതിനുള്ള വ്യക്തമായ ഒരു അബ്സ്ട്രാക്ഷൻ അവ നൽകുന്നു.
- നിയന്ത്രണം അത്യാവശ്യമാണ്: ഒരു നിയന്ത്രിത കൺകറൻസി പൂൾ റിസോഴ്സ് ക്ഷയിക്കുന്നത് തടയുകയും ബാഹ്യ സിസ്റ്റം പരിധികൾ പാലിക്കുകയും ചെയ്യുന്നു.
ജാവാസ്ക്രിപ്റ്റ് ഇക്കോസിസ്റ്റം വികസിച്ചുകൊണ്ടിരിക്കുമ്പോൾ, ഇറ്ററേറ്റർ ഹെൽപ്പർ പ്രൊപ്പോസൽ ഭാഷയുടെ ഒരു സ്റ്റാൻഡേർഡ് ഭാഗമായി മാറാൻ സാധ്യതയുണ്ട്, ഇത് സ്ട്രീം കൈകാര്യം ചെയ്യുന്നതിനുള്ള ശക്തവും നേറ്റീവുമായ ഒരു അടിസ്ഥാനം നൽകുന്നു. എന്നിരുന്നാലും, പാരലലൈസേഷനുള്ള ലോജിക്—`Promise.race` പോലുള്ള ഒരു ഉപകരണം ഉപയോഗിച്ച് പ്രോമിസുകളുടെ ഒരു പൂൾ കൈകാര്യം ചെയ്യുന്നത്—നിർദ്ദിഷ്ട പ്രകടന വെല്ലുവിളികൾ പരിഹരിക്കാൻ ഡെവലപ്പർമാർക്ക് നടപ്പിലാക്കാൻ കഴിയുന്ന ശക്തമായ, ഉയർന്ന തലത്തിലുള്ള ഒരു പാറ്റേണായി തുടരും.
ഇന്ന് നമ്മൾ നിർമ്മിച്ച `parallelMap` ഫംഗ്ഷൻ നിങ്ങളുടെ സ്വന്തം പ്രോജക്റ്റുകളിൽ എടുത്ത് പരീക്ഷിക്കാൻ ഞാൻ നിങ്ങളെ പ്രോത്സാഹിപ്പിക്കുന്നു. നിങ്ങളുടെ തടസ്സങ്ങൾ തിരിച്ചറിയുക, അവ API കോളുകളോ, ഡാറ്റാബേസ് പ്രവർത്തനങ്ങളോ, ഫയൽ പ്രോസസ്സിംഗോ ആകട്ടെ, ഈ കൺകറന്റ് സ്ട്രീം മാനേജ്മെൻ്റ് പാറ്റേൺ നിങ്ങളുടെ ആപ്ലിക്കേഷനുകളെ എങ്ങനെ വേഗതയേറിയതും, കൂടുതൽ കാര്യക്ഷമവും, ഡാറ്റാ-ഡ്രൈവൺ ലോകത്തിൻ്റെ ആവശ്യകതകൾക്ക് തയ്യാറാക്കിയതും ആക്കാമെന്ന് കാണുക.